﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceProcess;
using System.Text;

namespace SSLCertAutoUpdate
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AppConfig config = new AppConfig();
                GetLocalIPv4();
                if (string.IsNullOrEmpty(config.OpensslPath) || !File.Exists(config.OpensslPath))
                {
                    LogHelper.WriteLog("Openssl 路径有误");
                    return;
                }
                //if (string.IsNullOrEmpty(config.ServerName))
                //{
                //    LogHelper.WriteLog("服务名称不能为空");
                //    return;
                //}
                if (string.IsNullOrEmpty(config.CertSubject))
                {
                    LogHelper.WriteLog("Subject不能为空");
                    return;
                }
                if (string.IsNullOrEmpty(config.CertPath))
                {
                    LogHelper.WriteLog("证书路径不能为空");
                    return;
                }
                //if (!IsServiceExists(config.ServerName))
                //{
                //    LogHelper.Instance.WriteLine("{0}服务不存在", config.ServerName);
                //}

                if (CheckCertTime(Path.Combine(config.CertPath, config.ServerCertName)) == false)
                {
                    LogHelper.WriteLog("证书有效期校验失败，更新证书");
                    if (CreateNewCert(config))
                    {

                    }
                    else
                    {
                        LogHelper.WriteLog("更新证书失败");
                    }
                    //RestartServer(config.ServerName);
                }


            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("运行异常。", ex);
            }
        }
        private static void ProcessChild(string exe, string cmd, string workdir)
        {
            try
            {
                Console.WriteLine(exe + " " + cmd);
                Process process1 = new Process();
                process1.StartInfo = new ProcessStartInfo(exe, cmd);
                process1.StartInfo.WorkingDirectory = workdir;
                process1.OutputDataReceived += Process1_OutputDataReceived;
                process1.Start();
                process1.WaitForExit();
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("执行命令失败：" + cmd, ex);
            }
        }

        private static void Process1_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            LogHelper.WriteLog("Child: " + e.Data);
        }

        private static bool CreateNewCert(AppConfig config)
        {
            LogHelper.WriteLog("创建证书");
            try
            {
                string opensslDir = Path.GetDirectoryName(config.OpensslPath);
                opensslDir = new DirectoryInfo(opensslDir).FullName;
                string opensslPath = Path.GetFullPath(config.OpensslPath);
                if (File.Exists(Path.Combine(opensslDir, "server.pem")))
                    File.Delete(Path.Combine(opensslDir, "server.pem"));
                if (File.Exists(Path.Combine(opensslDir, "serkey.pem")))
                    File.Delete(Path.Combine(opensslDir, "serkey.pem"));
                var ips = GetLocalIPv4();
                string subject = config.CertSubject.Replace("{ip}", ips[0]);
                string cnfFile = Path.Combine(opensslDir, "openssl.cnf");
                string dstCnf = UpdateOpensslCnf(cnfFile, ips);
                string cmd1 = "req -config \"" + dstCnf + "\" -new -nodes -keyout serkey.pem -out sslreq.pem -newkey rsa:2048 -subj \"" + subject + "\"";
                string cmd2 = "ca -config  \"" + dstCnf + "\" -utf8 -out sslcert.pem -md sha256 -extensions ssl -batch -infiles sslreq.pem";
                string cmd3 = "x509 -in sslcert.pem -out server.pem";

                ProcessChild(config.OpensslPath, cmd1, opensslDir);
                ProcessChild(config.OpensslPath, cmd2, opensslDir);
                ProcessChild(config.OpensslPath, cmd3, opensslDir);


                if (File.Exists(Path.Combine(opensslDir, "sslcert.pem")))
                    File.Delete(Path.Combine(opensslDir, "sslcert.pem"));
                if (File.Exists(Path.Combine(opensslDir, "sslreq.pem")))
                    File.Delete(Path.Combine(opensslDir, "sslreq.pem"));

                string srcSerFile = Path.Combine(config.CertPath, config.ServerCertName);
                string srcSerKeyFile = Path.Combine(config.CertPath, config.ServerKeyName);
                if (File.Exists(srcSerFile + "_old"))
                    File.Delete(srcSerFile + "_old");
                if (File.Exists(srcSerKeyFile + "_old"))
                    File.Delete(srcSerKeyFile + "_old");
                if (File.Exists(srcSerFile))
                    File.Move(srcSerFile, srcSerFile + "_old");
                if (File.Exists(srcSerKeyFile))
                    File.Move(srcSerKeyFile, srcSerKeyFile + "_old");
                File.Move(Path.Combine(opensslDir, "server.pem"), srcSerFile);
                File.Move(Path.Combine(opensslDir, "serkey.pem"), srcSerKeyFile);

                if (CheckCertTime(srcSerFile))
                {
                    LogHelper.WriteLog("更新证书成功");
                    return true;
                }
                else
                    LogHelper.WriteLog("更新证书失败");
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("创建证书失败", ex);
            }

            return false;
        }

        /// <summary>
        /// 重启服务
        /// </summary>
        /// <param name="svcName"></param>
        private static void RestartServer(string svcName)
        {

            try
            {
                ServiceController ctrl = new ServiceController(svcName);
                ctrl.Refresh();
                if (ctrl.Status == ServiceControllerStatus.Running)
                {
                    ctrl.Stop();
                    ctrl.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 10));
                }
                ctrl.Start();
                ctrl.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 10));
                LogHelper.WriteLog("服务重启完成");
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("重启服务失败", ex);
            }
        }

        #region 查验服务是否存在

        /// <summary>
        /// 服务是否存在
        /// </summary>
        /// <param name="svcName"></param>
        /// <returns></returns>
        private static bool IsServiceExists(string svcName)
        {
            if (string.IsNullOrEmpty(svcName) == true)
            {
                return false;
            }
            bool flag = false;
            try
            {
                ServiceController control = new ServiceController(svcName);
                ServiceControllerStatus status = control.Status;//这一行必不可少
                if (control != null)
                {
                    flag = true;
                }
            }
            catch
            {
                flag = false;
            }
            return flag;
        }

        #endregion
        /// <summary>
        /// 检查证书时间
        /// </summary>
        /// <param name="certFile"></param>
        /// <returns></returns>
        private static Boolean CheckCertTime(string certFile)
        {
            if (File.Exists(certFile))
            {
                try
                {
                    StringBuilder builder = new StringBuilder();

                    var lines = File.ReadAllLines(certFile, Encoding.UTF8);
                    foreach (var item in lines)
                    {
                        if (item.StartsWith("--") && item.Contains("BEGIN CERTIFICATE"))
                        {
                            builder.Clear();
                            continue;
                        }
                        if (item.StartsWith("--") && item.Contains("END CERTIFICATE"))
                        {
                            break;
                        }
                        builder.Append(item.Trim());
                    }


                    System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(Convert.FromBase64String(builder.ToString()));

                    Console.WriteLine("Subject={0}", cert.Subject);
                    Console.WriteLine("NotAfter={0}", cert.NotAfter.ToString());
                    Console.WriteLine("NotBefore={0}", cert.NotBefore.ToString());
                    if (cert.NotAfter > DateTime.Now.AddDays(7))
                        return true;
                }
                catch (Exception ex)
                {
                    LogHelper.WriteLog("解析证书失败", ex);
                }
            }

            return false;
        }
        /// <summary>
        /// 获取本机IP
        /// </summary>
        /// <returns></returns>
        private static List<string> GetLocalIPv4()
        {
            List<string> result = new List<string>();
            result.Add("127.0.0.1");
            try
            {
                var iplist = Dns.GetHostAddresses(Dns.GetHostName());

                for (int i = 0; i < iplist.Length; i++)
                {
                    if (iplist[i].IsIPv6Teredo)
                        continue;
                    if (iplist[i].IsIPv6LinkLocal || iplist[i].IsIPv6SiteLocal || iplist[i].IsIPv6Multicast)
                        continue;
                    string ip = iplist[i].ToString();
                    if (ip.Contains(":"))
                        continue;
                    Console.WriteLine("ip: {0}", ip);
                    //return ip;
                    result.Add(ip);
                }


            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("获取本机IP失败", ex);
            }


            return result;
        }
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="ip"></param>
        private static string UpdateOpensslCnf(string cnfFile, List<string> ips)
        {
            if (ips == null || ips.Count < 1)
                return cnfFile;
            try
            {
                string dstFile = Path.Combine(Path.GetDirectoryName(cnfFile), "dst.cnf");
                if (File.Exists(dstFile))
                    File.Delete(dstFile);
                List<string> ipsFile = new List<string>();
                var lines = File.ReadAllLines(cnfFile);
                for (int i = 0; i < lines.Length; i++)
                {
                    if (lines[i].StartsWith("[alternate_names]"))
                    {
                        ipsFile.Add(lines[i]);
                        for(int j = 0; j < ips.Count; j++)
                        {
                            ipsFile.Add(string.Format("IP.{0} = {1}", j+1, ips[j]));
                        }
                    }
                    else if (lines[i].StartsWith("IP"))
                    {
                        continue;
                    }
                    else
                    {
                        ipsFile.Add(lines[i]);
                    }
                }


                File.WriteAllLines(dstFile, ipsFile.ToArray());

                return dstFile;

            }
            catch (Exception ex)
            {
                LogHelper.WriteLog("更新cnf配置失败", ex);
            }
            return cnfFile;
        }
    }
}
